package com.thomsonreuters.elektronunified;

import com.reuters.rfa.common.Client;
import com.reuters.rfa.common.Context;
import com.reuters.rfa.common.DispatchException;
import com.reuters.rfa.common.Dispatchable;
import com.reuters.rfa.common.EventQueue;
import com.reuters.rfa.common.EventSource;
import com.reuters.rfa.common.Handle;
import com.reuters.rfa.config.ConfigDb;
import com.reuters.rfa.dictionary.FieldDictionary;
import com.reuters.rfa.omm.OMMAttribInfo;
import com.reuters.rfa.omm.OMMElementList;
import com.reuters.rfa.omm.OMMEncoder;
import com.reuters.rfa.omm.OMMMsg;
import com.reuters.rfa.omm.OMMPool;
import com.reuters.rfa.omm.OMMTypes;
import com.reuters.rfa.rdm.RDMDictionary;
import com.reuters.rfa.rdm.RDMInstrument;
import com.reuters.rfa.rdm.RDMMsgTypes;
import com.reuters.rfa.rdm.RDMUser;
import com.reuters.rfa.session.Session;
import com.reuters.rfa.session.omm.OMMConsumer;
import com.reuters.rfa.session.omm.OMMItemIntSpec;
import com.thomsonreuters.elektronunified.RequestItem.EURequestTypeEnum;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.*;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class RFAManager implements RDMCallbacks  
{
	private static final boolean DEBUGEXTEND = true;
	
    private ClientCallback 	clientApp;
    private ConfigDb 		configDataBase; 
    private Session         session;
    private EventQueue      eventQueue;
    private OMMPool         ommPool;
    private OMMConsumer     ommConsumer;
    private FieldDictionary dictionary;
    private LoginClient     loginClient;
    
    public boolean 	sessionUpStatus = false;
    public String 	sessionUpStatusText = "";
    public boolean 	itemRequestStatusOK = false;
    public String 	itemRequestStatusText = "";
    public boolean 	loginComplete = false;
    public boolean 	loginFailed = false;
    public boolean 	dictionaryLoaded = false;
    public boolean 	dictionaryLoadFailed = false;
    
    private final Map<String, Handle> itemHandles = new HashMap<String, Handle>();
    private final Map<String, ArrayList<String>> userIdItems = new TreeMap<String, ArrayList<String>>();

    private Handle 	loginHandle;
    private Handle 	fieldDictionaryHandle;
    private Handle 	enumDictionaryHandle;

    private String 	serviceName;
    private String 	permServiceName;
    private String 	itemName;
    private short 	messageModel;
    private String 	clientid;

    public RFAManager(ClientCallback clientApp, String clientId)
    {
    	this.clientApp = clientApp;
    	clientid = clientId;
    	
        final String userName 	= clientid;
        final String appId 		= "196";
        final String position 	= "1.1.1.1/net";
        
        serviceName 			= "hERTU";
        permServiceName 		= "h&%EUEENT%&";
        String hostlist 		= "159.220.108.197,159.220.108.135";
        String port 			= "14002";
        Boolean debug 			= false;
        
        if ( DEBUGEXTEND ) {
        	
        	if ( System.getenv( "EUSNAME" ) != null ) {
        		serviceName = System.getenv( "EUSNAME" );
        	};
        	if ( System.getenv( "EUPSNAME" ) != null ) {
        		permServiceName = System.getenv( "EUPSNAME" );
        	};
        	if ( System.getenv( "EUHOST" ) != null ) {
        		hostlist = System.getenv( "EUHOST" );
        	};
        	if ( System.getenv( "EUPORT" ) != null ) {
        		port = System.getenv( "EUPORT" );
        	};
        	if ( System.getenv( "EUDEBUG" ) != null && System.getenv( "EUDEBUG" ).equalsIgnoreCase("true") ) {
        		debug = true;
        	};
        }

        final short messageModel = RDMMsgTypes.msgModelType("MARKET_PRICE");

        if (messageModel != RDMMsgTypes.MARKET_BY_ORDER
                && messageModel != RDMMsgTypes.MARKET_BY_PRICE
                && messageModel != RDMMsgTypes.MARKET_MAKER
                && messageModel != RDMMsgTypes.SYMBOL_LIST
                && messageModel != RDMMsgTypes.MARKET_PRICE) 
        {
            System.err.println("message-model-name must be MARKET_BY_ORDER, "
                    + "MARKET_BY_PRICE, MARKET_MAKER, SYMBOL_LIST or MARKET_PRICE");
            System.exit(1);
        }

        disableJavaLoggers();
        this.setItemName(itemName);
        this.messageModel = messageModel;
        
        configDataBase = new ConfigDb();
        configDataBase.addVariable("_Default.Sessions.RSSLSession.connectionList", "RSSLConnection");
        configDataBase.addVariable("_Default.Connections.RSSLConnection.connectionType", "RSSL");
        configDataBase.addVariable("_Default.Connections.RSSLConnection.serverList", hostlist);
        configDataBase.addVariable("_Default.Connections.RSSLConnection.portList", port );
        configDataBase.addVariable("_Default.Connections.RSSLConnection.logFileName", "none");
        if ( debug ) {
        	configDataBase.addVariable("_Default.Logger.AppLogger.fileLoggerEnabled", "true" );
        	configDataBase.addVariable("_Default.Connections.RSSLConnection.traceMsgHex", "true" );
        	configDataBase.addVariable("_Default.Connections.RSSLConnection.traceMsgToFile", "true" );
        	configDataBase.addVariable("_Default.Connections.RSSLConnection.traceMsgDomains", "all" );
        	configDataBase.addVariable("_Default.Connections.RSSLConnection.traceReqMsg", "true" );
        	configDataBase.addVariable("_Default.Connections.RSSLConnection.traceRespMsg", "true" );
        	configDataBase.addVariable("_Default.Connections.RSSLConnection.traceMsgFileName", "traceFile" );
        }

        Context.initialize(configDataBase);

        session = Session.acquire("RSSLSession");
        if (session == null) 
        {

            sessionUpStatusText = "Could not create session.";
            
        }
        else 
        {
 
            ommPool = OMMPool.create();
            
            ommConsumer = (OMMConsumer) session.createEventSource(
                    EventSource.OMM_CONSUMER, null);

            eventQueue = EventQueue.create(null);
           
            if (session != null) 
            {
              	long wait = 1000;
            	int count = 0;
            	
            	sendLoginRequest(userName, appId, position);
            	
            	while ( ! sessionUpStatus)
            	{
            	
            		runDispatch(wait);
            		count++;
  
             		if (loginFailed)
            		{
            			return;
            		}            	       		
  
            		if (dictionaryLoadFailed)
            		{
            			return;
            		}
  
            		if (count == 50)
            		{
            			sessionUpStatusText = "Could not connect. Is the VPN up?";
            			return;
            		}
            	
            	}
            	
            	return;
            }
        }
    }  

    private static void disableJavaLoggers()
    {
    	Logger globalLogger = Logger.getLogger("");
    	Handler[] handlers = globalLogger.getHandlers();
    	for(Handler handler : handlers) 
    	{
    	    globalLogger.removeHandler(handler);
    	}
    }

    private void sendLoginRequest(String userName, String appId, String position) 
    {
 
        OMMMsg loginReqMsg = encodeLoginReqMsg(userName, appId, position);
        OMMItemIntSpec loginIntSpec = new OMMItemIntSpec();
        loginIntSpec.setMsg(loginReqMsg);


        loginClient = new LoginClient(this);
        loginHandle = ommConsumer.registerClient( eventQueue, loginIntSpec, loginClient, null);        
       
        ommPool.releaseMsg(loginReqMsg);
    }


    private OMMMsg encodeLoginReqMsg(String user, String appId, String position) 
    {
 
    	OMMMsg msg = ommPool.acquireMsg();
        msg.setMsgType(OMMMsg.MsgType.REQUEST);
        msg.setMsgModelType(RDMMsgTypes.LOGIN);
        msg.setAttribInfo(null, user, RDMUser.NameType.USER_NAME);

        if (appId != null || position != null) 
        {

        	OMMEncoder encoder = ommPool.acquireEncoder();
            encoder.initialize(OMMTypes.MSG, 500);
            encoder.encodeMsgInit(msg, OMMTypes.ELEMENT_LIST, OMMTypes.NO_DATA);

            ommPool.releaseMsg(msg);

            encoder.encodeElementListInit(
                    OMMElementList.HAS_STANDARD_DATA, (short) 0, (short) 0);
            if (appId != null) 
            {
                encoder.encodeElementEntryInit(RDMUser.Attrib.ApplicationId,
                        OMMTypes.ASCII_STRING);
                encoder.encodeString(appId, OMMTypes.ASCII_STRING);
            }
            if (position != null) 
            {
                encoder.encodeElementEntryInit(RDMUser.Attrib.Position,
                        OMMTypes.ASCII_STRING);
                encoder.encodeString(position, OMMTypes.ASCII_STRING);
            }
            
            encoder.encodeAggregateComplete();

            msg = (OMMMsg) encoder.acquireEncodedObject();
            ommPool.releaseEncoder(encoder);
        }

        return msg;
    }

    public void sendRequest(RequestItem itemRequest)
    {
    
    	if ( itemRequest.getRequestType() == EURequestTypeEnum.subscription )
    	{
    		if ( itemRequest.itemArray.isEmpty() ) 
        	{
        		itemRequestStatusOK=false;
        		itemRequestStatusText="No items (e.g. TRI.N) specified in request.";
        		return;
        	}
    		
     		if ( ! itemRequest.getUserIdentifier().isEmpty() && ! itemRequest.getItemArray().isEmpty() )
    		{
     			if ( itemRequest.getUserIdentifier() == "PERM_PROFILE_REQ" ) {
     				// If permission request validate format UUID+IP+AID
     				ArrayList<String> itemArray = itemRequest.getItemArray();
     				for ( String item : itemArray ) {
     					if ( ! item.contains("+") ) {
     						itemRequestStatusOK = false;
     						itemRequestStatusText = "Invalid permission profile request. Must be of the form: UUID+IP+AID";
     						return;
     					}
     					String[] parts = item.split("\\+");
     					
     					if ( parts.length != 3 ) {
     						itemRequestStatusOK = false;
     						itemRequestStatusText = "Invalid permission profile request. Must have 3 parts of the form: UUID+IP+AID";
     						return;
     					}
     					
     					// Validate second part of permission request is valid IP
     					try {
							InetAddress.getByName(parts[1]);
						} catch (UnknownHostException e) {
							itemRequestStatusOK = false;
							itemRequestStatusText="Invalid IP address specified in the permission profile request";
							return;
						}
     				}
     			}
     			
     			addUserIdItems(itemRequest);
 	
    		}
    		sendSubscriptionRequest(itemRequest);  
    		
    	}
    	else if ( itemRequest.getRequestType() == EURequestTypeEnum.cancellation )
    	{ 
    		if ( itemRequest.getUserIdentifier().isEmpty() && itemRequest.getItemArray().isEmpty() )
    		{
        		itemRequestStatusOK=false;
        		itemRequestStatusText="No items (e.g. TRI.N) or user identifiers specified in request.";
        		return;
    		}
    		
    		if ( itemRequest.getUserIdentifier().isEmpty() || userIdItems.containsKey(itemRequest.getUserIdentifier()) )
    		{
    			sendCancellationRequest(itemRequest);
    		}
    		else 
    		{
        		itemRequestStatusOK=false;
        		itemRequestStatusText="Non existent user identifier specified in request.";
        		return;
    		}
 
    	}

    }
    
    private void sendSubscriptionRequest(RequestItem itemRequest)
    {

    	boolean view =(itemRequest.viewArray.isEmpty()) ? false : true;
    	boolean permReq = (itemRequest.getUserIdentifier() == "PERM_PROFILE_REQ") ? true : false;
    	
    	for (String itemName : itemRequest.getItemArray())
    	{
	        OMMMsg msg = ommPool.acquireMsg();
        	msg.setMsgType(OMMMsg.MsgType.REQUEST);
        	msg.setMsgModelType(messageModel); 
	        
	        if ( permReq ) {
	        	msg.setAttribInfo( permServiceName, itemName.split("\\+")[0], RDMInstrument.NameType.RIC );
	        } else {
	        	msg.setAttribInfo( serviceName, itemName, RDMInstrument.NameType.RIC );
	        
	        	if (view) 
	        	{
	        		msg.setIndicationFlags(OMMMsg.Indication.ATTRIB_INFO_IN_UPDATES | OMMMsg.Indication.VIEW);	        	
	        		OMMEncoder encoder = ommPool.acquireEncoder();
	        		encoder.initialize(OMMTypes.MSG, itemRequest.viewArray.size() * 5 + 100);
	            
	        		encoder.encodeMsgInit(msg, OMMTypes.NO_DATA, OMMTypes.ELEMENT_LIST);
	        		ommPool.releaseMsg(msg);
	        		encoder.encodeElementListInit(OMMElementList.HAS_STANDARD_DATA, (short)0, (short)0);
	            
	        		encoder.encodeElementEntryInit(RDMUser.View.ViewType, OMMTypes.UINT);
	        		encoder.encodeUInt(RDMUser.View.FIELD_ID_LIST);
	            
	        		encoder.encodeElementEntryInit(RDMUser.View.ViewData, OMMTypes.ARRAY);
	        		encoder.encodeArrayInit(OMMTypes.INT, 2);
	            
	        		for (String fldIdStr : itemRequest.getViewArray())
	        		{
	        			encoder.encodeArrayEntryInit();
	        			encoder.encodeInt(Integer.parseInt(fldIdStr));
	        		}
	            
	        		encoder.encodeAggregateComplete();
	            
	        		msg = (OMMMsg) encoder.acquireEncodedObject();
	        		ommPool.releaseEncoder(encoder);
	        	}
	        	else
	        	{
	        		msg.setIndicationFlags(OMMMsg.Indication.ATTRIB_INFO_IN_UPDATES);
	        	}
	        }
	        
	        OMMItemIntSpec intspec = new OMMItemIntSpec();
	        intspec.setMsg(msg);

	        Client client;

	        client = new MarketPriceClient( clientApp, dictionary, DEBUGEXTEND );
	
	        Handle handle = ommConsumer.registerClient( eventQueue, intspec, client, itemRequest.userIdentifier ); 
	        
	        itemHandles.put(itemName, handle);
	        
	        ommPool.releaseMsg(msg);
    	}
    	
		itemRequestStatusOK=true;
		String item_text = (itemRequest.getItemArray().size() == 1) ? "item" : "items";
		itemRequestStatusText=itemRequest.getItemArray().size() + " " + item_text + " requested for " + itemRequest.userIdentifier + ".";
		return;
    }

    private void sendCancellationRequest(RequestItem itemRequest)
    {
    	if ( ! itemRequest.getUserIdentifier().isEmpty())
    	{
    		ArrayList<String> uDTms = userIdItems.remove(itemRequest.getUserIdentifier());
    		if (uDTms != null)
    		{
	    		for (String itemname : uDTms)
	        	{    	
	    			Handle handle = itemHandles.remove(itemname);
	    			if (handle != null)
	    			{  			
	    				ommConsumer.unregisterClient(handle);
	    			}    			
	        	}
    		}
    		
    		itemRequestStatusOK=true;
    		itemRequestStatusText="All items cancelled for " + itemRequest.getUserIdentifier();

    	}
    	else if ( ! itemRequest.getItemArray().isEmpty() )
    	{
    		for (String itemname : itemRequest.getItemArray())
    		{
    			
    			Handle handle = itemHandles.remove(itemname);
    			if (handle != null)
    			{
    				ommConsumer.unregisterClient(handle);
    	    		itemRequestStatusOK=true;
    	    		itemRequestStatusText= itemname + " cancelled.";
    			}    			
    		}  			
    	}	
    }

    private void sendDictionaryRequests(String serviceName) 
    {
        OMMMsg msg = ommPool.acquireMsg();

        msg.setMsgType(OMMMsg.MsgType.REQUEST);
        msg.setMsgModelType(RDMMsgTypes.DICTIONARY);
        
        OMMAttribInfo attribInfo = ommPool.acquireAttribInfo();

        final String FIELD_DICTIONARY = "RWFFld";
        attribInfo.setName(FIELD_DICTIONARY);
        attribInfo.setServiceName(serviceName);
        attribInfo.setFilter(RDMDictionary.Filter.NORMAL);
        msg.setAttribInfo(attribInfo);

        OMMItemIntSpec intSpec = new OMMItemIntSpec();
        intSpec.setMsg(msg);

        DictionaryClient dictionaryClient = new DictionaryClient(this);

        fieldDictionaryHandle = ommConsumer.registerClient(eventQueue, intSpec, dictionaryClient, null);

        final String ENUM_DICTIONARY = "RWFEnum";
        attribInfo.setName(ENUM_DICTIONARY);
        msg.setAttribInfo(attribInfo);
        intSpec.setMsg(msg);

        enumDictionaryHandle = ommConsumer.registerClient(eventQueue, intSpec, dictionaryClient, null);

        ommPool.releaseMsg(msg);
        ommPool.releaseAttribInfo(attribInfo);
    }

    public void runDispatchLoop()
    {
        boolean active = true;
        while (active) 
        {
            try 
            {
                eventQueue.dispatch(Dispatchable.INFINITE_WAIT);
            }
            catch (DispatchException de) 
            {
                 active = false;
            }
        }
    }

    public int runDispatch() 
    {
    	int outStanding = 0;
        try 
        {
            outStanding = eventQueue.dispatch(Dispatchable.NO_WAIT);
        }
        catch (DispatchException de) 
        {
              //   
        }
        
        return outStanding;
    }
    
    public int runDispatch(long wait) 
    {
    	int outStanding = 0;

        try 
        {
        	  outStanding = eventQueue.dispatch(wait);
        }
        catch (DispatchException de)         
        {
        	
        }
          return outStanding;

    }

    public void cleanUp() 
    {
     	int events = 0;
    	while (events != -1)
    	{    		
    		events = runDispatch();
    	}
    	
        ommPool.destroy();
        ommConsumer.unregisterClient(loginHandle);
        eventQueue.destroy();
        ommConsumer.destroy();
        session.release();
        Context.uninitialize();
    }
    
    @Override
    public void onLoginSuccess() 
    {
    	loginComplete = true;
        sendDictionaryRequests(serviceName);
    }

    @Override
    public void onLoginFailure() 
    {
    	loginFailed = true;
    	sessionUpStatusText = "Login failed. Please check that the correct client identifier was provided.";
    	ommConsumer.unregisterClient(loginHandle);
    }


    @Override
    public void onDictionariesCompleted(FieldDictionary dict) 
    {
        dictionary = dict;
        dictionaryLoaded = true;
        sessionUpStatus = true;
        sessionUpStatusText = "System available for subscriptions to content.";
    }
    
    @Override
    public void onDictionariesInCompleted()
    {
    	dictionaryLoadFailed = true;
    	ommConsumer.unregisterClient(fieldDictionaryHandle);
    	ommConsumer.unregisterClient(enumDictionaryHandle);
    	sessionUpStatusText = "Connected but the EU service is not available. Please contact Thomson Reuters.";
    }
       
    public boolean getdictionaryLoaded() 
    {
    	return dictionaryLoaded;
    }

	public String getItemName() 
	{
		return itemName;
	}

	public void setItemName(String itemName) 
	{
		this.itemName = itemName;
	}
	
	public String getSessionUpStatusText() 
	{
		return  sessionUpStatusText;
	}
	
	private void addUserIdItems(RequestItem itemRequest)
	{		
	    if(userIdItems.containsKey(itemRequest.getUserIdentifier()))
	    {
	    	return;
	    }
	    else
	    {
	    	userIdItems.put(itemRequest.getUserIdentifier(), itemRequest.getItemArray());         
	    }
	}
		
}

	
	

